<template>
  <el-form
    ref="ref-el-form"
    v-loading="loading"
    v-bind="props"
    :model="model"
    v-on="events"
  >
    <component
      :is="showFeature(group) ? 'el-card' : 'div'"
      v-for="(group, groupIndex) in groups"
      :key="buildDOMKey(group, groupIndex)"
      v-bind="buildGroupProps(group)"
    >
      <div
        v-if="showFeature(group)"
        slot="header"
        class="group-title"
      >
        <i :class="icon(group)" />
        {{ group.title }}
        <div
          v-if="buttons(group) && buttons(group).length"
          class="group-action"
        >
          <el-button-package
            v-for="(item, index) in buttons(group)"
            :key="buildDOMKey(item, index)"
            :c-m="buildButtonCM(item)"
          />
        </div>
      </div>
      <el-row
        v-for="(row, rowIndex) in group.items"
        :key="buildDOMKey(row, rowIndex)"
        :ref="`ref-el-row-${rowIndex}`"
        v-bind="buildRowProps(group, row)"
        v-on="row.events"
      >
        <div
          v-if="showFeature(row)"
          v-bind="buildRowFeatureProps(group, row)"
        >
          <i :class="icon" />
          {{ text }}
          <span
            v-if="buttons(row) && buttons(row).length"
            class="row-action"
          >
            &lt;
            <el-button-package
              v-for="(item, index) in buttons(row)"
              :key="buildDOMKey(item, index)"
              :c-o-m="buildButtonCM(item)"
            />
            &gt;
          </span>
        </div>
        <el-col
          v-for="(col, colIndex) in row.items"
          :key="buildDOMKey(col, colIndex)"
          :ref="`ref-el-col-${rowIndex}-${colIndex}`"
          v-bind="buildColProps(row, rowIndex, col, colIndex)"
        >
          <el-form-item
            :ref="`ref-el-form-item-${rowIndex}-${colIndex}`"
            v-bind="col.props"
            v-on="col.events"
          >
            <template
              v-if="hasColLabelTip(col)"
              slot="label"
            >
              {{ col.props.label }}
              <el-tooltip>
                <div slot="content">
                  <span v-html="col.tip" />
                </div>
                <el-button
                  type="text"
                  icon="el-icon-antd-question-circle"
                />
              </el-tooltip>
            </template>
            <el-form-input-package
              v-for="(inputItem, inputIndex) in col.items"
              :key="buildDOMKey(inputItem, inputIndex)"
              :ref="`ref-el-form-input-${inputItem.name}`"
              :c-m="inputItem"
              :value="getModelValue(inputItem.name)"
              @input="handleInput($event, inputItem)"
            />
          </el-form-item>
        </el-col>
      </el-row>
    </component>
  </el-form>
</template>

<script lang="ts">
/*
prop 列表
  CM: ComponentModel  组件模型
  linkage: boolean    是否全局联动 [默认为 false]
  resetTo: any        表单可重置为
  loading: boolean    表单加载状态 [默认为 false]
CM.items.[i].meta 扩展
  span: number      栅格占据的列数 [参考 Col Attributes]
  offset: number    栅格左侧的间隔格数 [参考 Col Attributes]
  push: number      栅格向右移动格数 [参考 Col Attributes]
  pull: number      栅格向左移动格数 [参考 Col Attributes]
  tip: string       为 label 添加提示信息
  linkage: boolean  组件是否可联动
                    为了减少重复渲染提高效率，表单不会实时将 model 变化 @input 到父组件
                    有些组件值的变化会联动其它组件，此时需要指定其为可联动
表单内置了两类布局组件 ElFormGroup、ElFormRow，这两类组件只通过 CM.items.[i].tag 进行判断
表单为 ElFormGroup、ElFormRow 提供了快速构建模式
  正常情况下构建 ElFormGroup、ElFormRow 需要使用 items 进行多层嵌套构建
  表单提供一种快速构建约定，按照约定添加组件可以快速进行分组、换行
    举例：向 CM.items 中添加以下节点
      CM.items.push({ tag: 'el-input', name: 'c-01' })
      CM.items.push({ tag: 'el-form-row' })
      CM.items.push({ tag: 'el-input', name: 'c-02' })
      CM.items.push({ tag: 'el-input', name: 'c-03' })
      CM.items.push({ tag: 'el-input', name: 'c-04' })
      CM.items.push({ tag: 'el-input', name: 'c-05' })
      CM.items.push({ tag: 'el-input', name: 'c-06' })
      CM.items.push({ tag: 'el-form-group', text: 'group-01' })
      CM.items.push({ tag: 'el-input', name: 'c-07' })
      CM.items.push({ tag: 'el-form-row', text: 'row-01' })
      CM.items.push({ tag: 'el-input', name: 'c-08' })
      CM.items.push({ tag: 'el-input', name: 'c-09' })
      CM.items.push({ tag: 'el-form-row' })
      CM.items.push({ tag: 'el-input', name: 'c-10' })
      CM.items.push({ tag: 'el-input', name: 'c-11' })
      CM.items.push({ tag: 'el-input', name: 'c-12' })
      CM.items.push({ tag: 'el-form-group' })
      CM.items.push({ tag: 'el-input', name: 'c-13' })
      CM.items.push({ tag: 'el-input', name: 'c-14' })
      CM.items.push({ tag: 'el-input', name: 'c-15' })
    解读：组件按照顺序 push 到 items 中
      01. c-01 前未指定 group、row 系统会将为其指定默认的 group、row
          group、row 未指定 text，且 showFeature 默认为 false，因此未显示其 DOM 特性
      02. c-02 前指定了 row，c-02 到 c-06 会在新行中进行响应式渲染
          row 未指定 text，且 showFeature 默认为 false，因此未显示其 DOM 特性
      03. c-07 前指定了 group-01，c-07 到 c-12 会在新组中渲染
          group-01 指定了 text，由于 text 有值，showFeature 默认指定为 true，因此 group-01 将显示其 DOM 特性
          在此组中还指定了 2 个 row，因此 group 中将进行 3 次换行，c-07 在第 1 行、c-08 到 c-09 在第 2 行、c-10 到 c-12 在第 3 行
          row-01 指定了 text，由于 text 有值，showFeature 默认指定为 true，因此 row-01 将显示其 DOM 特性
      04. c-13 前指定了 group，c-13 到 c-15 会在新组中渲染
          group 未指定 text，且 showFeature 默认为 false，因此未显示其 DOM 特性
ref 列表
  ref-el-form [默认]
  ref-el-row-${rowIndex}
  ref-el-col-${rowIndex}-${colIndex}
  ref-el-form-item-${rowIndex}-${colIndex}
  ref-el-form-input-${inputItem.name}
*/
import { Component, Prop } from 'vue-property-decorator'
import { Form } from 'element-ui'
import TgComponent from '../TgComponent'
import { ComponentModel } from '@TG/interface'
import { setDefaultProps } from '@TG/kit/element/formBuild'
import { isNotEmpty } from '@TG/kit/validate'
import { appModule } from '@TG/store/modules/app'
import ElButtonPackage from './ElButtonPackage.vue'
import ElFormInputPackage from './ElFormInputPackage.vue'

@Component({
  name: 'ElFormPackage',
  components: {
    ElButtonPackage,
    ElFormInputPackage
  }
})
export default class extends TgComponent {
  @Prop({ type: Object, required: true }) private CM!: ComponentModel
  @Prop({ type: Boolean, default: false }) private linkage!: boolean
  @Prop({ type: Object }) private resetTo!: any
  @Prop({ type: Boolean, default: false }) private loading!: boolean

  private model: any = {}
  private colResponsiveSpan: { xs: number, sm: number, md: number, lg: number, xl: number } = {
    xs: 12,
    sm: 12,
    md: 8,
    lg: 6,
    xl: 4
  }

  get props() {
    this.setModel(this.CM.props?.get('model'))
    return Object.assign({ 'label-position': 'top' }, this.CM.props, this.$attrs)
  }

  get events() {
    return Object.assign({}, this.CM.events, this.$listeners)
  }

  get groups() {
    const groups: ComponentModel[] = []
    const items = this.CM.items || []
    if (isNotEmpty(items)) {
      setDefaultProps(...items)
      items.forEach((item, index) => {
        const tag = item.tag
        if (tag === 'el-form-group') {
          groups.push(item)
          return
        }
        // 指定默认 group
        if (groups.length === 0) {
          groups.push({ tag: 'el-form-group', items: [] })
        }
        const group = groups[groups.length - 1]
        const rows = group.items || []
        if (tag === 'el-form-row') {
          rows.push(item)
          return
        }
        if (rows.length === 0) {
          rows.push({ tag: 'el-form-row', items: [] })
        }
        const row = rows[rows.length - 1]
        const items = row.items || []
        items.push(item)
      })
    }
    return groups
  }

  private showFeature(item: ComponentModel) {
    const text = item.text
    const showFeature = item.meta?.showFeature || isNotEmpty(text)
    return showFeature
  }

  private icon(item: ComponentModel) {
    const tag = item.tag
    const defaultIcon = tag === 'el-form-group' ? 'el-icon-antd-appstore' : 'el-icon-antd-detail'
    const icon = item.meta?.icon || defaultIcon
    return icon
  }

  private buttons(item: ComponentModel) {
    const buttons = item.meta?.buttons || []
    return buttons
  }

  private buildButtonCM(buttonCM: ComponentModel) {
    buttonCM.props = Object.assign({}, buttonCM.props, { type: 'text' })
    return buttonCM
  }

  private buildGroupProps(group: ComponentModel) {
    const props = new Map<string, any>([
      ['shadow', 'never']
    ])
    const classes = []
    if (this.showFeature(group)) {
      classes.push('form-group')
    }
    props.set('class', 'classes')
    return props
  }

  private buildRowProps(group: ComponentModel, row: ComponentModel) {
    return {
      gutter: 18,
      ...row.props
    }
  }

  private buildRowFeatureProps(group: ComponentModel, row: ComponentModel) {
    const classes = ['row-title']
    if (!this.showFeature(group)) {
      classes.push('row-title-pl')
    }
    return {
      class: classes
    }
  }

  private buildColProps(row: ComponentModel, rowIndex: number, col: ComponentModel, colIndex: number) {
    const span: number = col.meta?.span || -1
    const offset: number = col.meta?.offset || 0
    const push: number = col.meta?.push || 0
    const pull: number = col.meta?.pull || 0
    const props: Map<string, any> = new Map<string, any>([
      ['offset', offset],
      ['push', push],
      ['pull', pull]
    ])
    if (span === -1) {
      // 未指定 span 则启用响应式配置
      Object.assign(props, this.colResponsiveSpan)
    } else {
      props.set('span', span)
    }
    const classes = [] // 样式 form-item 在组件上部增加虚线
    if (!this.showFeature(row) && rowIndex > 0) {
      classes.push('form-item')
    } else {
      // 计算每行最多可放置的组件数量
      let colCount = 0
      const width = appModule.pageSize.width
      if (width >= 1920) {
        colCount = 24 / this.colResponsiveSpan.xl
      } else if (width >= 1200) {
        colCount = 24 / this.colResponsiveSpan.lg
      } else if (width >= 992) {
        colCount = 24 / this.colResponsiveSpan.md
      } else if (width >= 768) {
        colCount = 24 / this.colResponsiveSpan.sm
      } else if (width < 768) {
        colCount = 24 / this.colResponsiveSpan.xs
      }
      // 当 colIndex 大于 colCount 说明进行了换行，添加样式 form-item
      // colIndex 下标从 0 开始，使用 >=
      if (colIndex >= colCount) {
        classes.push('form-item')
      }
    }
    props.set('class', classes)
    return props
  }

  private hasColLabelTip(item: ComponentModel) {
    const tip = item.meta?.tip || ''
    return isNotEmpty(tip)
  }

  private handleInput(value: any, inputItem: ComponentModel) {
    const name = inputItem.name || ''
    const linkage = inputItem.meta?.linkage
    const model = { ...this.model }
    this.setModelValue(value, name, model)
    this.setModel(model)
    if (linkage || (linkage === undefined && this.linkage)) {
      this.$emit('input', this.model)
    }
  }

  private setModelValue(value: any, name: string, model: any) {
    if (isNotEmpty(name)) {
      const fragments = name.split('.')
      const fragmentLen = fragments.length
      if (fragmentLen === 1) {
        model[name] = value
      } else {
        let valueRef = model
        fragments.forEach((fragment, i) => {
          if (i === fragmentLen - 1) {
            valueRef[fragment] = value
          } else {
            valueRef = valueRef[fragment]
          }
        })
      }
    }
  }

  public getModelValue(name: string) {
    let value: any = ''
    if (isNotEmpty(name)) {
      value = this.model
      name.split('.').forEach(fragment => {
        value = value[fragment]
      })
    }
    return value
  }

  private setModel(model: any) {
    if (isNotEmpty(model, true)) {
      this.model = model
    }
  }

  public getModel() {
    return { ...this.model }
  }

  public clearValidate() {
    const ref = this.ref()
    if (isNotEmpty(ref)) {
      (ref as Form).clearValidate()
    }
  }

  public validateForm() {
    this.clearValidate()
    const ref = this.ref()
    if (isNotEmpty(ref)) {
      return (ref as Form).validate()
    } else {
      return Promise.resolve(false)
    }
  }

  public clearForm() {
    const ref = this.ref()
    if (isNotEmpty(ref)) {
      (ref as Form).resetFields()
    }
  }

  public resetForm() {
    if (isNotEmpty(this.resetTo)) {
      this.$emit('input', this.resetTo)
    } else {
      this.clearForm()
    }
  }

  public defaultRef() {
    return 'el-form'
  }
}
</script>

<style lang="scss" scoped>
.form-group {
  margin-bottom: 18px;
}

.group-title {
  font-weight: bold;
}

.group-action {
  float: right;
  margin-top: -1px;
  .el-button--text {
    padding: 0px;
  }
}

.row-title {
  font-weight: bold;
  padding-bottom: 18px;
}

.row-title-pl {
  padding-left: 8px;
}

.row-action {
  .el-button--text {
    padding: 0px;
  }
}

.form-item {
  border-top: 1px dashed rgb(235, 238, 245);
}
</style>
